⚡ FAB ACADEMY  ·  Week 11: Networking & Communications  ·  XIAO ESP32-C3 + ESP32 + MQTT + Mosquitto

⚡ Week 11: Networking & Communications

FAB ACADEMY  |  XIAO ESP32-C3  |  ESP32  |  MQTT · MOSQUITTO · WIFI


// contents

  1. What I built
  2. What is MQTT
  3. Setting up Mosquitto
  4. Publisher — XIAO ESP32-C3 + LSM6DS33
  5. Subscriber — ESP32 + LED
  6. Results
  7. Reflection
  8. Files

// what I built

This week builds directly on the interface week. In Week 14 I had the XIAO ESP32-C3 reading the LSM6DS33 IMU over I2C and streaming data over USB serial to a Python backend. MQTT was part of the plan but I skipped it to focus on WebSocket and the dashboard first. This week I completed that piece and took it further — both boards now communicate wirelessly over WiFi through a local MQTT broker, with no USB cable involved.

The XIAO ESP32-C3 connects to WiFi and publishes live accelerometer and gyroscope readings to a topic on the Mosquitto broker running on my laptop. A second board — a standard ESP32 — subscribes to that same topic and watches the accelerometer values. When the sensor is shaken hard enough that the acceleration exceeds a threshold, the ESP32 turns on an LED. When it drops back below the threshold the LED turns off. Two completely separate boards, communicating wirelessly, with no direct connection between them.

XIAO ESP32-C3
reads LSM6DS33
MQTT broker
Mosquitto on laptop
ESP32
controls LED
BoardRoleTopic
XIAO ESP32-C3 + LSM6DS33 Publisher publishes sensor readings fab/imu
ESP32 Subscriber receives readings, controls LED fab/imu
Laptop (Mosquitto) Broker routes messages between boards all topics

// what is MQTT

MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol designed for devices that need to communicate over a network without talking to each other directly. Instead of Device A sending data straight to Device B, both connect to a central broker. Device A publishes a message to a topic — which is just a string like fab/imu — and Device B subscribes to that topic. The broker receives the published message and forwards it to every subscriber. The publisher and subscriber never need to know each other's IP address or even that the other exists.

This makes it very well suited to hardware projects — you can add more subscribers (a dashboard, a data logger, a second actuator) without changing anything on the publisher side. The broker handles all the routing.

// KEY POINT   Both boards connect to the broker's IP address (the laptop's IP on the local WiFi network). They don't connect to each other. The broker is the only thing they both need to know about.


// setting up mosquitto

Mosquitto is a lightweight open-source MQTT broker. I already had it installed from the interface week. To allow the ESP32 boards to connect over WiFi (rather than just localhost), Mosquitto needs a config file that allows connections from outside the laptop. By default it only accepts connections from the same machine.

I created a file called mosquitto.conf with the following:

listener 1883
allow_anonymous true

Then started the broker with:

mosquitto -c mosquitto.conf

To find the laptop's IP address on the local network (which the boards need to connect to) I ran ipconfig in the terminal and looked for the IPv4 address under the WiFi adapter — something like 192.168.x.x. Both boards use this address as the broker IP in their firmware.

// NOTE   The laptop and both ESP32 boards must all be on the same WiFi network. If the boards connect to a different network than the laptop, they cannot reach the broker.


// publisher — xiao esp32-c3

The XIAO ESP32-C3 already had the LSM6DS33 firmware from Week 14 which read the sensor and sent CSV over serial. For this week I rewrote the firmware to connect to WiFi and publish the readings directly to the MQTT broker over the network — no USB serial or Python backend needed. It uses the PubSubClient Arduino library for MQTT and the built-in WiFi library for the network connection.

On startup the board connects to WiFi and then connects to the broker at the laptop's IP address on port 1883. In the loop it reads the sensor, formats the 6 values as a JSON string and publishes it to fab/imu every 100ms. If the broker connection drops it automatically tries to reconnect.

#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_LSM6DS33.h>

const char* ssid     = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
const char* broker   = "192.168.x.x";  // laptop IP

WiFiClient wifi;
PubSubClient mqtt(wifi);
Adafruit_LSM6DS33 lsm;

void connectWifi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void connectMQTT() {
  while (!mqtt.connected()) {
    mqtt.connect("xiao-publisher");
    delay(500);
  }
}

void setup() {
  Serial.begin(115200);
  connectWifi();
  mqtt.setServer(broker, 1883);
  connectMQTT();
  lsm.begin_I2C();
  lsm.setAccelRange(LSM6DS_ACCEL_RANGE_8_G);
  lsm.setGyroRange(LSM6DS_GYRO_RANGE_500_DPS);
}

void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();

  sensors_event_t accel, gyro, temp;
  lsm.getEvent(&accel, &gyro, &temp);

  char payload[128];
  snprintf(payload, sizeof(payload),
    "{\"ax\":%.2f,\"ay\":%.2f,\"az\":%.2f,\"gx\":%.2f,\"gy\":%.2f,\"gz\":%.2f}",
    accel.acceleration.x, accel.acceleration.y, accel.acceleration.z,
    gyro.gyro.x, gyro.gyro.y, gyro.gyro.z);

  mqtt.publish("fab/imu", payload);
  delay(100);
}

// subscriber — esp32 + led

The ESP32 connects to the same WiFi network and subscribes to fab/imu. Every time a message arrives the onMessage callback fires. It parses the JSON payload, reads the three accelerometer values and calculates the total acceleration magnitude using:

magnitude = sqrt(ax*ax + ay*ay + az*az)

At rest the magnitude sits around 9.8 (gravity). When the sensor is shaken the magnitude spikes well above that. I set the threshold at 15 m/s² — anything above that turns the LED on, anything below turns it off. The threshold was tuned by watching the serial monitor while shaking the sensor at different intensities.

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

const char* ssid     = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
const char* broker   = "192.168.x.x";
const int   LED_PIN  = 2;
const float THRESHOLD = 15.0;

WiFiClient wifi;
PubSubClient mqtt(wifi);

void onMessage(char* topic, byte* payload, unsigned int length) {
  StaticJsonDocument<200> doc;
  deserializeJson(doc, payload, length);

  float ax = doc["ax"];
  float ay = doc["ay"];
  float az = doc["az"];
  float magnitude = sqrt(ax*ax + ay*ay + az*az);

  if (magnitude > THRESHOLD) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

void connectWifi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void connectMQTT() {
  while (!mqtt.connected()) {
    if (mqtt.connect("esp32-subscriber")) {
      mqtt.subscribe("fab/imu");
    }
    delay(500);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  connectWifi();
  mqtt.setServer(broker, 1883);
  mqtt.setCallback(onMessage);
  connectMQTT();
}

void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();
}

// LIBRARY NEEDED   The subscriber uses ArduinoJson to parse the JSON payload. Install it from Arduino Library Manager — search "ArduinoJson" by Benoit Blanchon.


// results

With both boards flashed and the broker running, the system worked as follows — the XIAO publishes sensor data continuously, the ESP32 receives it and the LED stays off at rest. Shaking the XIAO causes the LED on the ESP32 to turn on immediately, and it turns off again once the motion stops.


// reflection


// files